package com.cg.intertview;

import org.junit.Test;

import java.util.*;

/**
 * 程序员面试金典 16.25.LRU 缓存
 *
 * @program: LeetCode->Interview_16_25
 * @author: cg
 * @create: 2022-04-12 09:26
 **/
public class Interview_16_25 {

    @Test
    public void test16_25() {
        //LRUCache cache = new LRUCache(2);
        //cache.put(1, 1);
        //cache.put(2, 2);
        //System.out.println(cache.get(1));
        //cache.put(3, 3);
        //System.out.println(cache.get(2));
        //cache.put(4, 4);
        //System.out.println(cache.get(1));
        //System.out.println(cache.get(3));
        //System.out.println(cache.get(4));
        LRUCache cache = new LRUCache(2);
        System.out.println(cache.get(2));
        cache.put(2, 6);
        System.out.println(cache.get(1));
        cache.put(1, 5);
        cache.put(1, 2);
        System.out.println(cache.get(1));
        System.out.println(cache.get(2));
    }

}

/**
 * 设计和构建一个“最近最少使用”缓存，该缓存会删除最近最少使用的项目。缓存应该从键映射到值(允许你插入和检索特定键对应的值)，并在初始化时指定最大容量。当缓存被填满时，它应该删除最近最少使用的项目。
 * <p>
 * 它应该支持以下操作： 获取数据 get 和 写入数据 put 。
 * <p>
 * 获取数据 get(key) - 如果密钥 (key) 存在于缓存中，则获取密钥的值（总是正数），否则返回 -1。
 * 写入数据 put(key, value) - 如果密钥不存在，则写入其数据值。当缓存容量达到上限时，它应该在写入新数据之前删除最近最少使用的数据值，从而为新的数据值留出空间。
 * <p>
 * 示例:
 * LRUCache cache = new LRUCache(2); //缓存容量
 * cache.put(1,1);
 * cache.put(2,2);
 * cache.get(1);       // 返回  1
 * cache.put(3,3);    // 该操作会使得密钥 2 作废
 * cache.get(2);       // 返回 -1 (未找到)
 * cache.put(4,4);    // 该操作会使得密钥 1 作废
 * cache.get(1);       // 返回 -1 (未找到)
 * cache.get(3);       // 返回  3
 * cache.get(4);       // 返回  4
 */
class LRUCache {

    static class LRUNode {
        int key;
        int value;
        LRUNode per;
        LRUNode next;

        public LRUNode() {
        }

        public LRUNode(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }

    /**
     * 哈希表
     */
    private HashMap<Integer, LRUNode> map;
    /**
     * 当前缓存大校
     */
    private int size;
    /**
     * 容量
     */
    private final int capacity;
    /**
     * 头节点
     */
    private LRUNode head;
    /**
     * 尾节点
     */
    private LRUNode tail;

    public LRUCache(int capacity) {
        map = new HashMap<>();
        this.size = 0;
        this.capacity = capacity;
        head = new LRUNode();
        tail = new LRUNode();
        head.next = tail;
        tail.per = head;
    }

    public int get(int key) {
        // 通过哈希表定位
        LRUNode node = map.get(key);
        // key不存在
        if (node == null) {
            return -1;
        }
        // key存在，移动至双向链表头部并返回
        moveToHead(node);
        return node.value;
    }

    public void put(int key, int value) {
        // 通过哈希表定位
        LRUNode node = map.get(key);
        if (node == null) {
            // key不存在，创建新节点
            LRUNode newNode = new LRUNode(key, value);
            // 将节点加入哈希表
            map.put(key, newNode);
            // 并将节点添加至双向链表头部
            addToHead(newNode);
            // 链表大小加1
            size++;
            if (size > capacity) {
                // 超出容量，移除最后一个节点
                LRUNode tail = removeTail();
                // 并在哈希表中也移除
                map.remove(tail.key);
                // 链表大小减1
                size--;
            }
        } else {
            // key存在，先修改value的值
            node.value = value;
            // 再移动至双向链表的头部
            moveToHead(node);
        }
    }

    /**
     * 将节点添加至头部
     *
     * @param node
     */
    private void addToHead(LRUNode node) {
        node.per = head;
        node.next = head.next;
        head.next.per = node;
        head.next = node;
    }

    /**
     * 移除节点
     *
     * @param node
     */
    private void removeNode(LRUNode node) {
        node.per.next = node.next;
        node.next.per = node.per;
        node.next = null;
        node.per = null;
    }

    /**
     * 移动节点至头部
     *
     * @param node
     */
    private void moveToHead(LRUNode node) {
        removeNode(node);
        addToHead(node);
    }

    /**
     * 删除最后一个节点
     *
     * @return
     */
    private LRUNode removeTail() {
        LRUNode node = tail.per;
        removeNode(node);
        return node;
    }

}

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache obj = new LRUCache(capacity);
 * int param_1 = obj.get(key);
 * obj.put(key,value);
 */